package us.gaaoc.framework.util.pdf.regexp;

import java.awt.Color;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jregex.Matcher;
import jregex.Pattern;


import org.apache.log4j.Logger;
import org.pdfbox.cos.COSString;
import org.pdfbox.pdmodel.PDDocument;
import org.pdfbox.pdmodel.PDPage;
import org.pdfbox.pdmodel.font.PDFont;
import org.pdfbox.util.PDFTextStripper;
import org.pdfbox.util.TextPosition;

import us.gaaoc.framework.util.io.FileUtility;

import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfStamper;


/**
 * This is a class that applies regular expressions to a PDF
 * document and replaced named groups with the given key/value pairs.
 * 
 * Example: 
 *  >  for.This the({SIGN_DAY}.*)day of({SIGN_MONTH}.*), 2008
 * will have SIGN_DAY and SIGN_MONTH replaced if they are in the given map.
 * 	
 * @author artripa
 */
public class PDFRegexp extends PDFTextStripper {


	Logger log = Logger.getLogger(PDFRegexp.class);
	
	char prevCharacter = 0;
	
	/**
	 * A single PDF text object
	 * @author artripa
	 */
	class TextCoord
	{
		TextPosition textPos;
		public String text;
		public float xpos;
		public float ypos;
		public PDFont font;
		COSString cosString;
	}
	
	public String documentToString()
	{
		String str = "";
		int i = 0;
		for(TextCoord t : document)
		{
			str += t.text;	
			i++;
		}
		return str;
	}
	
	private List<TextCoord> document = new java.util.ArrayList<TextCoord>();
	
	public List<TextCoord> getDocument()
	{
		return document;
	}
	
	public PDFRegexp() throws PDFRegexpException,IOException {
		super();
		this.setSortByPosition(true);
		this.setShouldSeparateByBeads(false);
		try {			
			bf = BaseFont.createFont(
					BaseFont.HELVETICA,
					BaseFont.WINANSI, BaseFont.EMBEDDED);
		} catch (DocumentException e) {
			throw new PDFRegexpException(e);
		}
	}
	
	PdfReader reader;
	PdfStamper stamper;
	int stage = 0;
	int currentPageNumber = 1;
	String resultString;
	String srcString;
	PDDocument currentDoc;
	PDPage currentPage;
	BaseFont bf = null;
	TextCoord tc_start; 
	TextCoord tc_end; 
	boolean freestandingStamp = false;
	int index = 0;
	PdfContentByte over;
	boolean stopStamping = false;
	int pageNumber = 0;
	String buffer = "";
	float textY = 0;
	
	public static float getWidthOfCharacter(String str,BaseFont font)
	{
		if(str.equals("#"))
		{
			return (float)0.0;
			
		}else{
			return font.getWidthPoint(str,11);
		}
	}
	
	
	boolean resetPosition = true;
	
	public float getFontSize()
	{
		//return 8;
		return 12;
	}
	
	float textOffset;
	private int ignoreIndex = 0;
	
	@Override 
	protected void showCharacter(TextPosition text)
	{
		//log.trace(text.getCharacter());
		if(stage == 0)
		{
			String str = text.getCharacter();
			if(str.length() > 1)
			{
				for(int i = 0; i!=str.length();i++)
				{
					TextCoord t = new TextCoord();
					t.font = text.getFont();
					t.xpos = text.getX();
					t.ypos = text.getY();
					t.text = Character.toString(str.charAt(i));
					t.textPos = text;
					document.add(t);
				//	log.trace(str.charAt(i));
				}

			}else{
				TextCoord t = new TextCoord();
				t.font = text.getFont();
				t.xpos = text.getX();
				t.ypos = text.getY();
				t.text = text.getCharacter();
				t.textPos = text;
				//log.trace(text.getCharacter());
				document.add(t);	
			}


		}else{




			boolean found = true;
			if(freestandingStamp)
			{
				found = ((int)text.getY() == (int)tc_start.textPos.getY() &&
						(int)text.getX() >= (int)tc_start.textPos.getX());	
			}else{
				found = ((int)text.getY() == (int)tc_end.textPos.getY() &&
						(int)text.getX() <= (int)tc_end.textPos.getX()) ||
						((int)text.getY() == (int)tc_start.textPos.getY() &&
								(int)text.getX() >= (int)tc_start.textPos.getX());
			}


			if(found && freestandingStamp && !stopStamping)						  
			{
				try{
					if(!resultString.substring(index,index+1).equals(srcString.substring(index,index+1)))
					{
						log.trace("writing: " +resultString.substring(index));
						over.beginText();
						over.setFontAndSize(bf, this.getFontSize());		
						log.debug("1 - setting font size to "+this.getFontSize());
						over.setColorFill(new Color(148, 0, 219));			
						over.showTextAligned(Element.ALIGN_LEFT, resultString.substring(index), text.getX() + 3,currentPage.getMediaBox().getHeight()- text.getY(),0);
						over.endText();
						stopStamping = true;
					}
				}catch(Exception exn)
				{

				}
				index+=text.getCharacter().length();

			}
			else if(found && !stopStamping)
			{
				try{
					
					//boolean f = false;
					textOffset = text.getX();
					for(char x : text.getCharacter().toCharArray())
					{					
						
										
						//log.trace(srcString.substring(index,index+1) + " " + resultString.substring(index,index+1) + " " + text.getCharacter());

						if(!resultString.substring(index,index+1).equals(srcString.substring(index,index+1) ) && ignoreIndex == 0)
						{
							int i = index;
							try{
							while(!resultString.substring(i,i+1).equals(srcString.substring(i,i+1)))
							{
								
								buffer += resultString.substring(i,i+1);
								log.trace("buffer: " + buffer + " idx: " + Integer.toString(i));
								i+=1;
							}
							}catch(IndexOutOfBoundsException exn)
							{
								
							}
							log.trace("out of buffer");
							resetPosition = false;
							over.beginText();
							over.setFontAndSize(bf,this.getFontSize());
							log.debug("2 - setting font size to "+this.getFontSize());
							over.setColorFill(new Color(148, 0, 219));
							log.trace("writing: " +buffer + "[]" + Float.toString(textOffset));
							
							over.showTextAligned(Element.ALIGN_JUSTIFIED_ALL,buffer.trim(), 6+textOffset,currentPage.getMediaBox().getHeight()-text.getY(),0);
							
							over.endText();
							ignoreIndex=buffer.length();
							
							//f = true;
							buffer = "";
							
							//index += buffer.length();
							
							//textOffset += bf.getWidthPointKerned(resultString.substring(index,index+1).trim(),this.getFontSize());
							/*
							log.trace("writing: " +resultString.substring(index,index+1) + "[]");
							over.beginText();
							over.setFontAndSize(bf,this.getFontSize());
							over.setColorFill(new Color(148, 0, 219));
							over.showTextAligned(Element.ALIGN_LEFT,resultString.substring(index,index+1).trim(), 3+textOffset,792-text.getY(),0);
							textOffset += bf.getWidthPointKerned(resultString.substring(index,index+1).trim(),this.getFontSize());
							
							over.endText();
							*/
							textY = currentPage.getMediaBox().getHeight()-text.getY();
							//f = true;
						}else{
							if(ignoreIndex!=0)
							{
								ignoreIndex--;
							}
							//textOffset = -1;
							resetPosition = true;
							log.trace("not writing: " +resultString.substring(index,index+1) + "[x]" + Float.toString(textOffset)  + " " +bf.getWidthPoint(Character.toString(x),11));
							textOffset += bf.getWidthPoint(Character.toString(x),11);
							//textOffset = text.getX();
							//textOffset += bf.getWidthPoint(Character.toString(x),12);
						
						}
						
						index++;
						prevCharacter = x;
					}
					//textOffset = -1;
				}catch(StringIndexOutOfBoundsException exn)
				{
					//textOffset = 0;
				}
			}
		}
		
	}


	
	
	/**
	 * Replaces the named groups from the regexp in the source
	 * from the given targets map.
	 * @param expression The regular expression used
	 * @param targets Key/Value pairs where the key is the named group and the value is the replaced text
	 * @param source The string being applied
	 * @param m The compiled regular expression object
	 * @return The text after replacing the matched data from the source
	 */
	@SuppressWarnings("unchecked")
	protected String replaceNamedTargets(String expression,Map<String,String> targets,String source,Matcher m)
	{
		String dest = source;
		
		
		//Order the list of targets by where they are located in
		//regular expression.
		Map<Integer,String> offsets = new HashMap<Integer,String>();
		for(String k : targets.keySet())
		{
			if(expression.indexOf(k) != -1)
			{
				offsets.put(new Integer(expression.indexOf(k)),k);
			}
		}

		List<Integer> sortedSet = new ArrayList(offsets.keySet());
		Collections.sort(sortedSet);
		
		
		for(Integer i : sortedSet)
		{
			String k = offsets.get(i);
			String tmp = "";
			try{
				for (int x = 0; x!=Math.abs(m.group(k).length() - targets.get(k).length());x++)
				{
					tmp += " ";
				}
				
				if(m.group(k).length() <= targets.get(k).length() && !freestandingStamp)
				{
					tmp = "";
					targets.put(k,targets.get(k).substring(0,m.group(k).length()));
				}
				String a = m.group(k);
				String b = targets.get(k) + tmp;
				
				dest = dest.replaceFirst(a,b);
			}catch(java.lang.IllegalArgumentException exn)
			{
				exn.printStackTrace();
			}
		}
		return dest;
	}	
	
	
	/**
	 * Applies a regular expression against a PDF serializes as an array of bytes.
	 * 
	 * 
	 * @param data The PDF as a byte array
	 * @param regexp The searching regular expression
	 * @param bStamp True if the targets to stamp are supposed to be stamped without wrapping/size/length restrictions. 
	 * Usually False when regex is inside of prose, True on floating stamps.
	 * @param targets The targets to stamp
	 * @return The modified PDF byte array
	 * @throws PDFRegexpException
	 */
	@SuppressWarnings("unchecked")
	public byte[] applyRegexp(byte[] data, String regexp,boolean bStamp,Map<String,String> targets,int pageStart,int pageEnd) throws PDFRegexpException
	{	
		
		
		
		
		//TODO:
		// store PDDocumentInstances, bytearrays, and search-able form 
		// so that many regexps can be called without having to re-create/re-load the data structures.		
		PDDocument pd = null;
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		boolean found = false;
			try {
				pd = PDDocument.load(new ByteArrayInputStream(data));
		
				
				this.freestandingStamp = bStamp;
				
				this.stopStamping = false;
				
				
				if(pageEnd == -1) pageEnd = pd.getDocumentCatalog().getAllPages().size();
				if(pageEnd > pd.getDocumentCatalog().getAllPages().size()) pageEnd = pd.getDocumentCatalog().getAllPages().size();
				
				this.currentPageNumber = pageStart+1;
				
				for(PDPage page : (List<PDPage>)pd.getDocumentCatalog().getAllPages().subList(pageStart,pageEnd))
				{
					
					textOffset = 0;	
					stage = 0;
					index = 0;
					// 	serialize document into a searchable form
					try {
						processStream(page, page.getResources(), page.getContents().getStream());
					} catch (IOException e) {
						throw new PDFRegexpException(e);
					}
	
					stage = 1;
					index = 0;
					textOffset = -1;
					// Apply regexp to that document
					String documentContent = documentToString();
			        Pattern p = new Pattern(regexp);
					Matcher m = p.matcher(documentContent);
					found = m.find();
					if(found) 
					{
							
			
						String SOURCE = documentContent.substring(m.start(),m.end());
						String DST = replaceNamedTargets(regexp,targets, SOURCE, m);
						
						//  iterate over document, finding text targets, and stamp them
						tc_start = getDocument().get(m.start());
						tc_end = getDocument().get(m.end());
						ignoreIndex = 0;
						currentDoc = pd;
						currentPage = page;
						ByteArrayInputStream inStream = new ByteArrayInputStream(data);
						try {
							reader = new PdfReader(inStream);
						} catch (IOException e) {
							throw new PDFRegexpException(e);
						}
						
						try {
							stamper = new PdfStamper(reader, out);
						} catch (DocumentException e) {
							throw new PDFRegexpException(e);
						} catch (IOException e) {
							throw new PDFRegexpException(e);
						}
						resultString = DST;
						srcString = SOURCE;
						over = stamper.getOverContent(currentPageNumber);
			
						try {
							//TODO: for standalone stamps, use a second function beside 
							// showcharacter/processStream
							processStream(page, page.getResources(), page.getContents().getStream());
						} catch (IOException e) {
							throw new PDFRegexpException(e);
						}
						
						if(buffer.length() != 0)
						{
							over.beginText();
							over.setFontAndSize(bf,this.getFontSize());
							log.debug("3 - setting font size to "+this.getFontSize());
							over.setColorFill(new Color(148, 0, 219));
							log.trace("writing-end: " +buffer + "[]");
							over.showTextAligned(Element.ALIGN_JUSTIFIED_ALL,buffer.trim(), textOffset,textY,0);
							textOffset += bf.getWidthPointKerned(buffer.trim(),this.getFontSize());
							over.endText();
							//f = true;
							buffer = "";
							
						}
			
						try {
							stamper.close();
						} catch (DocumentException e) {
							throw new PDFRegexpException(e);
						} catch (IOException e) {
							throw new PDFRegexpException(e);
						}
						break;
					}
					currentPageNumber++;
				}
		
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}finally{			
				try {
					if(pd!=null)
						pd.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
	
		
		if(!found)
		{
			throw PDFRegexpException.notFound(regexp);
		}
		
		return out.toByteArray();		
	}
	

	/**
	 * @param args
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		PDFRegexp reg = new PDFRegexp();
		PDFRegexp reg2 = new PDFRegexp();
		String pdfFile = "/home/artripa/Documents/Fulton_Complaint_For_Recovery_Of_Child_Support.pdf";
		
		//THIS IS THE SECTION FOR THE SHOW CAUSE
		//byte[] data = FileUtility.readFileInByteArray(new java.io.FileInputStream("/srv/documents/./109/0/ff8081811ed6825e011ed68335460011_V5"));///home/artripa/ShowCauseAnnotated.pdf"));
		//byte[] data = FileUtility.readFileInByteArray(new java.io.FileInputStream("/srv/documents/./109/0/ff8081811ed6a3f3011ed6a4ef220010_V1"));///home/artripa/ShowCauseAnnotated.pdf"));
		byte[] data = FileUtility.read2list(pdfFile);
		
		
		//String regexp = "day of.*({COURT_MONTH}.*), 2008 Superior Courtroom";
		//String regexp = DocumentRegexps.CourtUuid3_SUMMONS_AND_RULE_NISI;
		String regexp = DocumentRegexps.CourtUuid3_SIGN_DATE;
		
		// THIS IS THE SECTION FOR SUMMONS/RULENISI
		//byte[] data2 = FileUtility.readFileInByteArray(new java.io.FileInputStream("/srv/documents/ff8081811d638bd3011d96e24c3201f2_V5"));		
		//String regexp2 = DocumentRegexps.CourtUuid3_SUMMONS_AND_RULE_NISI;

		List<String> regexps = new java.util.LinkedList<String>();
		regexps.add(regexp);
		Map<String,String> targets  = new java.util.HashMap<String,String>();
		
		/*
		targets.put("COURT_TIME","3:00 p.m.");
		targets.put("COURT_DAY","12");
		targets.put("COURT_MONTH_YEAR","January, " + Integer.toString(2009));
		targets.put("COURT_YEAR","2009");
		targets.put("COURT_ROOM","555"); //this is padded in PDFStamperController
		*/
		targets.put("SIGN_DAY","12");
		targets.put("SIGN_MONTH_YEAR", "Januarary,_XXXX");
		data = reg.applyRegexp(data,regexp,false, targets,0,2);
		
		targets.clear();
		targets.put("SIGN_DAY","15");
		targets.put("SIGN_MONTH_YEAR", "XXXXXXXX,_2001");
		data = reg2.applyRegexp(data,regexp,false, targets,2,3);
		
		FileOutputStream out = new FileOutputStream("/tmp/tmp01.pdf");

		
		

		
		out.write(data);
		out.close();

		
	}

}
